#region Namespaces

using System;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;

#endregion

namespace Vibstudio.X4NET.Xml.Linq
{
    internal class XNodeNavigator : XPathNavigator
    {
        private static readonly XAttribute attributeNamespaceXml = new XAttribute(XNamespace.Xmlns.GetName("xml"), XNamespace.Xml.NamespaceName);

        private readonly XmlNameTable _nameTable;

        private XAttribute _attribute;

        private XNode _node;

        public XNodeNavigator(XNode node, XmlNameTable nameTable)
        {
            _node = node;
            _nameTable = nameTable;
        }

        public XNodeNavigator(XNodeNavigator other)
        {
            _node = other._node;
            _attribute = other._attribute;
            _nameTable = other._nameTable;
        }

        public override string BaseURI
        {
            get { return _node.BaseUri ?? String.Empty; }
        }

        public override bool CanEdit
        {
            get { return true; }
        }

        public override bool HasAttributes
        {
            get
            {
                if (_attribute != null)
                {
                    return false;
                }
                XElement el = _node as XElement;
                if (el == null)
                {
                    return false;
                }
                foreach (var at in el.Attributes())
                {
                    if (!at.IsNamespaceDeclaration)
                    {
                        return true;
                    }
                }
                return false;
            }
        }

        public override bool HasChildren
        {
            get
            {
                if (_attribute != null)
                {
                    return false;
                }
                XContainer c = _node as XContainer;
                return c != null && c.FirstNode != null;
            }
        }

        public override bool IsEmptyElement
        {
            get
            {
                if (_attribute != null)
                {
                    return false;
                }
                XElement el = _node as XElement;
                return el != null && el.IsEmpty;
            }
        }

        public override string LocalName
        {
            get
            {
                switch (NodeType)
                {
                    case XPathNodeType.Namespace:
                        return _attribute.Name.Namespace == XNamespace.None ? String.Empty : _attribute.Name.LocalName;
                    case XPathNodeType.Attribute:
                        return _attribute.Name.LocalName;
                    case XPathNodeType.Element:
                        return ((XElement)_node).Name.LocalName;
                    case XPathNodeType.ProcessingInstruction:
                        return ((XProcessingInstruction)_node).Target;
                    default:
                        return String.Empty;
                }
            }
        }

        public override string Name
        {
            get
            {
                XName name;
                switch (NodeType)
                {
                    case XPathNodeType.Attribute:
                        name = _attribute.Name;
                        break;
                    case XPathNodeType.Element:
                        name = ((XElement)_node).Name;
                        break;
                    default:
                        return LocalName;
                }
                if (name.Namespace == XNamespace.None)
                {
                    return name.LocalName;
                }
                XElement el = (_node as XElement) ?? _node.Parent;
                if (el == null)
                {
                    return name.LocalName;
                }
                string prefix = el.GetPrefixOfNamespace(name.Namespace);
                return prefix.Length > 0 ? String.Concat(prefix, ":", name.LocalName) : name.LocalName;
            }
        }

        public override string NamespaceURI
        {
            get
            {
                switch (NodeType)
                {
                    case XPathNodeType.ProcessingInstruction:
                    case XPathNodeType.Namespace:
                        return String.Empty;
                    case XPathNodeType.Attribute:
                        return _attribute.Name.NamespaceName;
                    case XPathNodeType.Element:
                        return ((XElement)_node).Name.NamespaceName;
                    default:
                        return String.Empty;
                }
            }
        }

        public override XmlNameTable NameTable
        {
            get { return _nameTable; }
        }

        public override XPathNodeType NodeType
        {
            get
            {
                if (_attribute != null)
                {
                    return _attribute.IsNamespaceDeclaration
                               ? XPathNodeType.Namespace
                               : XPathNodeType.Attribute;
                }
                switch (_node.NodeType)
                {
                    case XmlNodeType.Element:
                        return XPathNodeType.Element;
                    case XmlNodeType.Document:
                        return XPathNodeType.Root;
                    case XmlNodeType.Comment:
                        return XPathNodeType.Comment;
                    case XmlNodeType.ProcessingInstruction:
                        return XPathNodeType.ProcessingInstruction;
                    default:
                        return XPathNodeType.Text;
                }
            }
        }

        public override string Prefix
        {
            get
            {
                XName name;
                switch (NodeType)
                {
                    case XPathNodeType.ProcessingInstruction:
                    case XPathNodeType.Namespace:
                        return String.Empty;
                    case XPathNodeType.Attribute:
                        name = _attribute.Name;
                        break;
                    case XPathNodeType.Element:
                        name = ((XElement)_node).Name;
                        break;
                    default:
                        return LocalName;
                }
                if (name.Namespace == XNamespace.None)
                {
                    return String.Empty;
                }
                XElement el = (_node as XElement) ?? _node.Parent;
                if (el == null)
                {
                    return String.Empty;
                }
                return el.GetPrefixOfNamespace(name.Namespace);
            }
        }

        public override IXmlSchemaInfo SchemaInfo
        {
            get { return null; }
        }

        public override object UnderlyingObject
        {
            get { return _attribute != null ? (object)_attribute : _node; }
        }

        public override string Value
        {
            get
            {
                if (_attribute != null)
                {
                    return _attribute.Value;
                }
                switch (NodeType)
                {
                    case XPathNodeType.Comment:
                        return ((XComment)_node).Value;
                    case XPathNodeType.ProcessingInstruction:
                        return ((XProcessingInstruction)_node).Data;
                    case XPathNodeType.Text:
                        string s = String.Empty;
                        for (var xn = _node as XText; xn != null; xn = xn.NextNode as XText)
                        {
                            s += xn.Value;
                        }
                        return s;
                    case XPathNodeType.Element:
                    case XPathNodeType.Root:
                        return GetInnerText((XContainer)_node);
                }
                return String.Empty;
            }
        }

        private string GetInnerText(XContainer xnode)
        {
            StringBuilder sb = null;
            foreach (XNode n in xnode.Nodes())
            {
                GetInnerText(n, ref sb);
            }
            return sb != null ? sb.ToString() : String.Empty;
        }

        private void GetInnerText(XNode n, ref StringBuilder sb)
        {
            switch (n.NodeType)
            {
                case XmlNodeType.Element:
                    foreach (XNode c in ((XElement)n).Nodes())
                    {
                        GetInnerText(c, ref sb);
                    }
                    break;
                case XmlNodeType.Text:
                case XmlNodeType.CDATA:
                    if (sb == null)
                    {
                        sb = new StringBuilder();
                    }
                    sb.Append(((XText)n).Value);
                    break;
            }
        }

        public override XPathNavigator Clone()
        {
            return new XNodeNavigator(this);
        }

        public override bool IsSamePosition(XPathNavigator other)
        {
            XNodeNavigator nav = other as XNodeNavigator;
            if (nav == null || nav._node.Owner != _node.Owner)
            {
                return false;
            }
            return _node == nav._node && _attribute == nav._attribute;
        }

        public override bool MoveTo(XPathNavigator other)
        {
            XNodeNavigator nav = other as XNodeNavigator;
            if (nav == null || nav._node.Document != _node.Document)
            {
                return false;
            }
            _node = nav._node;
            _attribute = nav._attribute;
            return true;
        }

        public override bool MoveToFirstAttribute()
        {
            if (_attribute != null)
            {
                return false;
            }
            XElement el = _node as XElement;
            if (el == null || !el.HasAttributes)
            {
                return false;
            }
            foreach (XAttribute a in el.Attributes())
            {
                if (!a.IsNamespaceDeclaration)
                {
                    _attribute = a;
                    return true;
                }
            }
            return false;
        }

        public override bool MoveToFirstChild()
        {
            if (_attribute != null)
            {
                return false;
            }
            XContainer c = _node as XContainer;
            if (c == null || c.FirstNode == null)
            {
                return false;
            }
            _node = c.FirstNode;
            _attribute = null;
            return true;
        }

        public override bool MoveToFirstNamespace(XPathNamespaceScope scope)
        {
            if (NodeType != XPathNodeType.Element)
            {
                return false;
            }
            for (XElement el = _node as XElement; el != null; el = el.Parent)
            {
                foreach (XAttribute a in el.Attributes())
                {
                    if (a.IsNamespaceDeclaration)
                    {
                        _attribute = a;
                        return true;
                    }
                }
                if (scope == XPathNamespaceScope.Local)
                {
                    return false;
                }
            }
            if (scope != XPathNamespaceScope.All)
            {
                return false;
            }
            _attribute = attributeNamespaceXml;
            return true;
        }

        public override bool MoveToId(string id)
        {
            throw new NotSupportedException("This XPathNavigator does not support IDs");
        }

        public override bool MoveToNext()
        {
            XNode xn = _node.NextNode;
            if (xn is XText)
            {
                for (; xn != null; xn = xn.NextNode)
                {
                    if (!(xn.NextNode is XText))
                    {
                        break;
                    }
                }
            }
            if (xn == null)
            {
                return false;
            }
            _node = xn;
            _attribute = null;
            return true;
        }

        public override bool MoveToNextAttribute()
        {
            if (_attribute == null)
            {
                return false;
            }
            if (_attribute.NextAttribute == null)
            {
                return false;
            }
            for (XAttribute a = _attribute.NextAttribute; a != null; a = a.NextAttribute)
            {
                if (!a.IsNamespaceDeclaration)
                {
                    _attribute = a;
                    return true;
                }
            }
            return false;
        }

        public override bool MoveToNextNamespace(XPathNamespaceScope scope)
        {
            if (_attribute == null)
            {
                return false;
            }
            for (XAttribute a = _attribute.NextAttribute; a != null; a = a.NextAttribute)
            {
                if (a.IsNamespaceDeclaration)
                {
                    _attribute = a;
                    return true;
                }
            }

            if (scope == XPathNamespaceScope.Local)
            {
                return false;
            }

            if (_attribute == attributeNamespaceXml)
            {
                return false;
            }

            for (XElement el = _attribute.Parent.Parent; el != null; el = el.Parent)
            {
                foreach (XAttribute a in el.Attributes())
                {
                    if (a.IsNamespaceDeclaration)
                    {
                        _attribute = a;
                        return true;
                    }
                }
            }
            if (scope != XPathNamespaceScope.All)
            {
                return false;
            }
            _attribute = attributeNamespaceXml;
            return true;
        }

        public override bool MoveToParent()
        {
            if (_attribute != null)
            {
                _attribute = null;
                return true;
            }
            if (_node.Owner == null)
            {
                return false;
            }
            _node = _node.Owner;
            return true;
        }

        public override bool MoveToPrevious()
        {
            if (_node.PreviousNode == null)
            {
                return false;
            }
            _node = _node.PreviousNode;
            _attribute = null;
            return true;
        }

        public override void MoveToRoot()
        {
            _attribute = null;
            if (_node.Document != null)
            {
                _node = _node.Document;
            }
            else
            {
                while (_node.Owner != null)
                {
                    _node = _node.Owner;
                }
            }
        }
    }
}